;; The first four lines of this file were added by Dracula.
;; They tell DrScheme that this is a Dracula Modular ACL2 program.
;; Leave these lines unchanged so that DrScheme can properly load this file.
#reader(planet "reader.rkt" ("cce" "dracula.plt") "modular" "lang")
(require "Imodifiedavltree.lisp")
(require "Istockhelper.lisp")
(require "Iperformance.lisp")
(require "Ivolatility.lisp")
(require "Ivolume.lisp")
(require "Iavgperformance.lisp")
(require "Icodec.lisp")

(module Mcodec-private
  (import Imodifiedavltree)
  (import Istockhelper)
  (import Iperformance)
  (import Ivolatility)
  (import Ivolume)
  (import Iavgperformance)
  
  (set-state-ok T)
  (include-book "io-utilities" :dir :teachpacks)
  (include-book "list-utilities" :dir :teachpacks)

#| *** i/o helpers ***
|#
;; reads input text file as a list of strings
; new string indicated by #\Newline character
(defun input-as-strings (str)
  (map-chrs->str (remove nil (packets-set (list (code-char 10) (code-char 13)) (str->chrs str)))))
#| -------------------------------------------|#

#| *** some other helpers ***
|#
;; converts a list of strings to a list of rational numbers
; used for data part when read data of tickers from data file
(defun strs->rats (strs)
  (if (consp strs)
      (cons (str->rat (car strs)) (strs->rats (cdr strs)))
      nil))
#| -------------------------------------------|#

#| *** functions to handle data file ***
|#
;; gets tokens which are seperated by #\, for later storing into an avl tree 
; for each data string of a ticker on a specific day
(defun get-data-strs (strs)
  (if (consp strs)
      (cons (map-chrs->str (packets #\, (str->chrs (car strs)))) (get-data-strs (cdr strs)))
      nil))

;; transform each part of each data string into correct data type
(defun transform-data (data-strs)
  (if (consp data-strs)
      (let* ((first-data (car data-strs))
            (date (first first-data))
            (ticker (second first-data))
            (data (nthcdr 2 first-data)))
      (cons (list (str->int date) ticker (strs->rats data))
            (transform-data (cdr data-strs))))
      nil))

;; gets the list of all data
(defun get-data-list (str)
  (transform-data (get-data-strs (input-as-strings str))))

;; builds the avl tree with each node is an element of the data list
; key of a node is a pair (Ticker, YYYYMMDD)
; data of a node is the rest of the element
(defun build-avl-tree (accumulator data-list)
  (if (consp data-list)
      (let* ((first-data (car data-list))
            (date (first first-data))
            (ticker (second first-data))
            (data (third first-data)))
      (build-avl-tree (avl-insert accumulator (list ticker date) data) (cdr data-list)))
      accumulator))

;; transforms data string into an avl tree
(defun data-to-avl (str)
  (build-avl-tree (empty-tree) (get-data-list str)))

;; reads and stores data text file into an avl tree
(defun read-and-transform-data (data-file-path state)
  (mv-let (data-as-str error state)
          (file->string data-file-path state)
     (if error
         (mv error state)
         (mv (data-to-avl data-as-str) state))))
#| -------------------------------------------|#

#| *** functions to handle data file ***
|#
;; gets tokens which are seperated by #\| for later storing into a list of strings 
; for each query string
(defun get-query-strs (strs)
  (if (consp strs)
      (cons (map-chrs->str (packets #\| (str->chrs (car strs)))) (get-query-strs (cdr strs)))
      nil))

;; gets the list of all queries
(defun get-query-list (str)
  (get-query-strs (input-as-strings str)))

;; reads and stores query text file into a list of strings
(defun read-and-transform-query (query-file-path state)
  (mv-let (query-as-str error state)
          (file->string query-file-path state)
     (if error
         (mv error state)
         (mv (get-query-list query-as-str) state))))
#| -------------------------------------------|#

#| *** functions to format all query's result ***
|#
;; converts a list of strings into a single string
; seperated by ;
(defun strs->str (str-list)
  (if (consp (cdr str-list))
      (concatenate 'string (car str-list) ";" (strs->str (cdr str-list)))
      (car str-list)))

;; response format for performance and volatility query result
; format "Ticker-1,Ticker-2,...,Ticker-n performance/volatility"
; (for it is possible that more than one ticker have the highest performance/volatility)
(defun performance/volatility-query-result-response (qr)
  (concatenate 'string (strs->str (first qr)) " " (rat->str (second qr) 1) "%"))

;; gets response for a query
(defun get-response (query ticker-list flat-data-avl)
  (let* ((query-type (first query))
         (start-date-str (second query))
         (start-date (str->int start-date-str))
         (end-date-str (third query))
         (end-date (str->int end-date-str))
         (to-query-tickers (nthcdr 3 query))
         (to-query-tickers-str (strs->str to-query-tickers))
         (all-tickers (strip-cars ticker-list)))
    (cond
      ; performance query format:
      ; performance|start-date|end-date[|ticker]*|[ticker]+ highest-performance
      ((string-equal query-type "performance")
       (if (equal to-query-tickers nil)
           (concatenate 'string query-type "|" start-date-str "|" end-date-str "|"
                        (performance/volatility-query-result-response (performance-query start-date end-date all-tickers ticker-list flat-data-avl)))
           (concatenate 'string query-type "|" start-date-str "|" end-date-str "|" to-query-tickers-str "|"
                        (performance/volatility-query-result-response (performance-query start-date end-date to-query-tickers ticker-list flat-data-avl)))))
      ; volatility query format:
      ; volatility|start-date|end-date[|ticker]*|[ticker]+ highest-volatility
      ((string-equal query-type "volatility")
       (if (equal to-query-tickers nil)
           (concatenate 'string query-type "|" start-date-str "|" end-date-str "|"
                        (performance/volatility-query-result-response (volatility-query start-date end-date all-tickers ticker-list flat-data-avl)))
           (concatenate 'string query-type "|" start-date-str "|" end-date-str "|" to-query-tickers-str "|"
                        (performance/volatility-query-result-response (volatility-query start-date end-date to-query-tickers ticker-list flat-data-avl)))))
      ; volume query format:
      ; volume|start-date|end-date[|ticker]*|[ticker,volume]+
      ((string-equal query-type "volume")
       (if (equal to-query-tickers nil)
           (concatenate 'string query-type "|" start-date-str "|" end-date-str "|"
                        (strs->str (volume-query start-date end-date all-tickers ticker-list flat-data-avl)))
           (concatenate 'string query-type "|" start-date-str "|" end-date-str "|" to-query-tickers-str "|"
                        (strs->str (volume-query start-date end-date to-query-tickers ticker-list flat-data-avl)))))
      ; avg-performance query format:
      ; avg-performance|start-date|end-date[|ticker]*|[ticker,avg-performance]+
      ((string-equal query-type "avg-performance")
       (if (equal to-query-tickers nil)
           (concatenate 'string query-type "|" start-date-str "|" end-date-str "|"
                        (strs->str (avg-performance-query start-date end-date all-tickers ticker-list flat-data-avl)))
           (concatenate 'string query-type "|" start-date-str "|" end-date-str "|" to-query-tickers-str "|"
                        (strs->str (avg-performance-query start-date end-date to-query-tickers ticker-list flat-data-avl))))))))

;; gets responses for all queries
(defun get-responses (query-list ticker-list flat-data-avl)
  (if (consp query-list)
      (cons (concatenate 'string
                         (get-response (car query-list) ticker-list flat-data-avl)
                         (string (code-char 10))
                         (string (code-char 13)))
            (get-responses (cdr query-list) ticker-list flat-data-avl))
      nil))

#| *** main function ***
|#
;; to run this program
; press run and in the run window of DrRacket
; type in (stock-market-analysis "path of data file" "path of query file" "path of output file")
(defun stock-market-analysis (data-file query-file output-file) 
  (mv-let (data-as-avl state)
          (read-and-transform-data data-file state)
          (mv-let (query-list state)
                  (read-and-transform-query query-file state)
                  (string-list->file output-file 
                                     (get-responses query-list
                                                    (get-ticker-list data-as-avl)
                                                    (avl-flatten data-as-avl)) 
                  state))))
#| -------------------------------------------|#

  (export Icodec))

(link Mcodec
  (import Imodifiedavltree Istockhelper Iperformance Ivolatility Ivolume Iavgperformance)
  (export Icodec)
  (Mcodec-private))

